Skip to content

feat(usage): real Claude subscription usage bars (5h + weekly) - #150

Merged
DIodide merged 1 commit into
stagingfrom
feat/agent-usage-real
Jun 21, 2026
Merged

feat(usage): real Claude subscription usage bars (5h + weekly)#150
DIodide merged 1 commit into
stagingfrom
feat/agent-usage-real

Conversation

@DIodide

@DIodide DIodide commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Problem

The AGENT USAGE panel showed only an estimated cost (the SDK's total_cost_usd, e.g. "$6.40 today · $6.40 wk") for Claude Code credentials. For an OAuth/subscription credential the meaningful number is the real 5-hour + weekly limit consumption — what Claude Code's /usage shows — not a dollar estimate.

Why it wasn't there already

The app already captures _meta._claude/rateLimit from the ACP stream and renders one bar from it (AccountLimitsSection) — but driving a real agent showed that signal is empty (agent_usage meta_keys=[]). The current Claude Agent SDK / ACP doesn't surface subscription usage. The dedicated GET /api/oauth/usage endpoint needs a user:profile scope the inference token lacks (403).

What does work (verified against the real token): the anthropic-ratelimit-unified-5h-* / -7d-* / -7d_sonnet-* headers ride on every /v1/messages response — the exact source Claude Code's /usage reads.

Change

  • FastAPI (usage.py): fetch_subscription_usage() makes the cheapest possible /v1/messages ping (max_tokens: 1) with the credential's OAuth token and parses the unified headers into a multi-window { buckets: { five_hour, seven_day, seven_day_sonnet } } snapshot (utilization 0–1, reset timestamps). Best-effort — returns None on any error rather than disrupting a turn.
  • Gateway (session_manager.py): at the turn boundary, when the SDK carries no rate-limit meta, fire a debounced (≤1/min per credential) background fetch that resolves the OAuth token, p ings, and persists the snapshot via the existing recordRateLimit. OAuth-only (api-key creds have no subscription windows and are skipped). No schema change — lastRateLimit is already v.any().
  • Frontend (usage-display.tsx): accountUsagesFromRateLimit now parses the multi-window buckets shape into an ordered array (keeping the legacy flat single-window as a fallback), and AccountLimitsSection renders one ProgressBar per window — reusing the existing budget-bar UI verbatim.

Verified live

Drove a real Claude Code agent (Daytona sandbox, real OAuth credential). The ping fired (POST api.anthropic.com/v1/messages 200), the snapshot stored, and the panel rendered:

Claude account limits — Current session 14% (resets 3h 42m) · Current week 46% (resets 1d 16h) · Current week (Sonnet) 7%

Tests

  • FastAPI: 7 new cases (header → buckets parsing, 429 still reports windows, network error → None). Full suite green.
  • Frontend: multi-window parser tests (0–1 → 0–100 normalization, ordering, rejected-window, flat fallback). Full suite green; tsc + biome clean.

Note on labels

Claude subscriptions don't have a literal "daily" limit — the short window is a 5-hour rolling session. The bars are labeled accordingly ("Current session" + "Current week") with live reset times, matching Claude Code's /usage. The estimated cost line is kept below as supplementary detail.

🤖 Generated with Claude Code

@DIodide
DIodide merged commit 9e54b77 into staging Jun 21, 2026
4 checks passed
DIodide added a commit that referenced this pull request Jun 22, 2026
fix(usage): harden subscription-usage fetch (review follow-ups to #150)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant